﻿using Nemerle;
using Nemerle.Collections;
using Nemerle.Text;
using Nemerle.Utility;
using Nemerle.Assertions;

using System;
using System.Collections.Generic;
using System.Linq;

using Nemerle2.Compiler;
using MSCci = Microsoft.Cci;
using System.Diagnostics;

namespace MetadataReader.CCI
{
  [DebuggerDisplay("Attribute: {Name}")]
  class CciCustomAttribute : ICustomAttribute
  {
    public this ([NotNull] provider : CciMetadataProvider, [NotNull] attribute : MSCci.ICustomAttribute)
    {
      _provider  = provider;
      _attribute = attribute;

      NamedArguments          = attribute.NamedArguments.Map(arg => (arg.ArgumentName.Value, GetAttributeValue(arg.ArgumentValue)));
      FullName                = Utils.GetFullName(attribute.Type); // Actually we don't need to load type to know Attribute name
      Name                    = (attribute.Type :> MSCci.INamedTypeReference).Name.Value;
      PositionalArguments     = attribute.Arguments.Map(GetAttributeValue);
      PositionalArgumentCount = PositionalArguments.Length;
    }

    _provider  : CciMetadataProvider;
    _attribute : MSCci.ICustomAttribute;

    public FullName                             : string                               { get; }
    public Name                                 : string                               { get; }
    public Type                                 : ITypeInfo
    {
      [Memoize] get { _provider.GetTypeInfo(_attribute.Type) }
    }
    public PositionalArgumentCount              : int                                  { get; private set; }
    public ArgumentValue[argIndex : int]        : CustomAttributeValue
    {
      get { PositionalArguments.Nth(argIndex) }
    }
    public NamedArgumentValue[argName : string] : CustomAttributeValue
    {
      get { NamedArguments.First((name, _) => name == argName)[1] }
    }
    public NamedArguments                       : list[string * CustomAttributeValue]  { get; private set; }
    public PositionalArguments                  : list[CustomAttributeValue]           { get; private set; }

    public HasNamedArgument(argName : string)   : bool
    {
      NamedArguments.Exists((name, _) => name == argName);
    }

    private GetAttributeValue(val : MSCci.IMetadataExpression) : CustomAttributeValue
    {
      //assert2(!val.Type.ToString().Contains("CSharpTestAssembly.TestEnum"));
      match (val)
      {
        | const is MSCci.IMetadataConstant    => GetConstantAttributeValue(const);
        | arr   is MSCci.IMetadataCreateArray => GetArrayAttributeValue(arr);
        | t     is MSCci.IMetadataTypeOf      => CustomAttributeValue.Type(_provider.GetType(t.TypeToGet))
        | _                                   => assert(false, $"Unknown metadata expression ($val)");
      }
    }

    private GetArrayAttributeValue(val : MSCci.IMetadataCreateArray) : CustomAttributeValue.Array
    {
      CustomAttributeValue.Array(val.Initializers
        .Select(arrItem => GetConstantAttributeValue(arrItem :> MSCci.IMetadataConstant))
        .ToArray());
    }

    private GetConstantAttributeValue(val : MSCci.IMetadataConstant) : CustomAttributeValue
    {
      if (val.Type.TypeCode == MSCci.PrimitiveTypeCode.NotPrimitive)
      {
        def type = _provider.GetType(val.Type);
        assert(type.IsEnum);
        CustomAttributeValue.Enum(Convert.ToInt64(val.Value), type)
      }
      else match (val.Value)
      {
        | val is bool     => CustomAttributeValue.Bool(val)
        | val is byte     => CustomAttributeValue.Byte(val)
        | val is char     => CustomAttributeValue.Char(val)
        | val is decimal  => CustomAttributeValue.Decimal(val)
        | val is double   => CustomAttributeValue.Double(val)
        | val is float    => CustomAttributeValue.Float(val)
        | val is Guid     => CustomAttributeValue.Guid(val)
        | val is int      => CustomAttributeValue.Int(val)
        | val is long     => CustomAttributeValue.Long(val)
        | val is sbyte    => CustomAttributeValue.SByte(val)
        | val is short    => CustomAttributeValue.Short(val)
        | val is string   => CustomAttributeValue.String(val)
        | val is uint     => CustomAttributeValue.UInt(val)
        | val is ulong    => CustomAttributeValue.ULong(val)
        | val is ushort   => CustomAttributeValue.UShort(val)
        | val             => assert(false, $"Unknown object type ($(val.GetType()))")
      }
    }

    public override ToString() : string
    {
      def args = PositionalArguments.Select(_.ToString())
        .Union(NamedArguments.Select(namedValue => $"$(namedValue[0]) = $(namedValue[1])"));
      $<#$Name(..$(args; ", "))#>
    }
  }
}
